using System;
using System.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;
using NWrapper.Config;

namespace NWrapper.Cache
{
	/// <summary>
	/// A proxy cache.
	/// </summary>
	public sealed class CacheProxy : ICache, IDisposable
	{
		private ICache _cache;
		private bool _disposed = false;

		#region Constructors
        /// <summary>
        /// The constructor will initialize the proxy using the given cache type.
        /// </summary>
        /// <param name="settings">The settings.</param>
		public CacheProxy(CacheSettings settings)
		{
			Initialize(settings);
		}
		#endregion

		#region Properties
		/// <summary>
		/// Returns a reference to the internal cached instance used by this proxy.
		/// </summary>
		public ICache InternalCache
		{
			get { return _cache; }
		}
		#endregion

		#region ICache Members
		/// <summary>
		/// Initializes the proxy by creating the underlying cache implementation.
		/// The underlying cache type is provided with the constructor method.
		/// </summary>
		/// <param name="settings">The cache settings.</param>
		/// <remarks>
		/// Repeated calls to this method will dispose the existing underlying
		/// cache implementation before re-creating it. As a result, this method
		/// can be used to refresh the underlying cache.
		/// </remarks>
		public void Initialize(CacheSettings settings)
		{
			DisposeCache();

			string assemblyName;
			string className;

			switch (settings.Type)
			{
				case CacheType.None:
					assemblyName = "NWrapper";
					className = "NWrapper.Cache.NullCache";
					break;
				case CacheType.Custom:
					assemblyName = settings.Assembly;
					className = settings.Class;
					break;
				case CacheType.AspNet:
					assemblyName = "NWrapper";
					className = "NWrapper.Cache.AspNetCache";
					break;
				case CacheType.Memcached:
					assemblyName = "NWrapper.Cache.Memcached";
					className = "NWrapper.Cache.Memcached.MemcachedCache";
					break;
				case CacheType.EnterpriseLibrary:
					assemblyName = "NWrapper.Cache.EnterpriseLibrary";
					className = "NWrapper.Cache.EnterpriseLibrary.EnterpriseLibraryCache";
					break;
				default:
					throw new ConfigurationErrorsException("Specified cache type in configuration is unknown or invalid");
			}
			
			_cache = ReflectionHelper.GetObject<ICache>(assemblyName, className);
			_cache.Initialize(settings);
		}

		/// <summary>
		/// Gets an object from the cache, or adds an object to the cache, using the given key.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <returns>The object from the cache, or null if not found.</returns>
		public object this[object key]
		{
			get { return _cache.Get(key); }
			set { _cache.Set(key, value); }
		}

		/// <summary>
		/// Gets an object from the cache, or adds an object to the cache, using the given key.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <returns>The object from the cache, or null if not found.</returns>
		public object this[string key]
		{
			get { return _cache.Get(key); }
			set { _cache.Set(key, value); }
		}

		/// <summary>
		/// Gets an object from the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <returns>The object from the cache, or null if not found.</returns>
		public object Get(object key)
		{
			return _cache.Get(key);
		}

		/// <summary>
		/// Adds an object to the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <param name="value">The object to cache.</param>
		public void Set(object key, object value)
		{
			_cache.Set(key, value);
		}

		/// <summary>
		/// Adds an object to the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <param name="value">The object to cache.</param>
		/// <param name="timeToExpire">The length of time before this object should be expired from the cache.</param>
		/// <remarks>
		/// If the underlying cache does not support time-based expiration, the object will never expire.
		/// </remarks>
		public void Set(object key, object value, TimeSpan timeToExpire)
		{
			_cache.Set(key, value, timeToExpire);
		}

		/// <summary>
		/// Adds an object to the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <param name="value">The object to cache.</param>
		/// <param name="expiration">The date/time when this object should be expired from the cache.</param>
		/// <remarks>
		/// If the underlying cache does not support time-based expiration, the object will never expire.
		/// </remarks>
		public void Set(object key, object value, DateTime expiration)
		{
			_cache.Set(key, value, expiration);
		}

		/// <summary>
		/// Determines whether an object is present in the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <returns>True if the key is registered in the cache; false otherwise.</returns>
		/// <remarks>
		/// Note that if the cache maintains keys for null objects, then this will return true, even though
		/// the object in the cache has a null value. To determine whether an item exists in the cache and
		/// has a not null value, use the <see cref="ContainsData"/> method.
		/// </remarks>
		public bool Contains(object key)
		{
			return _cache.Contains(key);
		}

		/// <summary>
		/// Determines whether an object is present in the cache and has a value other than null.
		/// </summary>
		/// <param name="key">The cache key.</param>
		/// <returns>True if the key is registered in the cache and is not equal to null; false otherwise.</returns>
		/// <remarks>
		/// Note that if the cache maintains keys for null objects, then this will return false when the cache
		/// key has an object value of null. To determine whether an item exists in the cache regardless of its
		/// value, use the <see cref="Contains"/> method.
		/// </remarks>
		public bool ContainsData(object key)
		{
			return (_cache.Get(key) != null);
		}

		/// <summary>
		/// Removes an item from the cache.
		/// </summary>
		/// <param name="key">The cache key.</param>
		public void Remove(object key)
		{
			_cache.Remove(key);
		}

		/// <summary>
		/// Clears all items from the cache.
		/// </summary>
		public void Clear()
		{
			_cache.Clear();
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Disposes the proxy and any underlying cache implementations.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Manages the disposal of the proxy and underlying cache.
		/// </summary>
		private void Dispose(bool disposing)
		{
			if (_disposed)
				return;

			if (disposing)
			{
				DisposeCache();
			}

			_disposed = true;
		}

		/// <summary>
		/// Disposes the underlying cache implementation.
		/// </summary>
		private void DisposeCache()
		{
			if (_cache != null && _cache is IDisposable)
			{
				((IDisposable)_cache).Dispose();
			}
			_cache = null;
		}
		#endregion
	}
}
